﻿namespace  Codeplex.KinkumaFramework.Interactivity
{
    using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interactivity;
    using System.Windows.Data;
    using System.Threading;

#if SILVERLIGHT
    using System.Windows.Controls;
#endif

    /// <summary>
    /// AssociateObjectのElabledプロパティに対してCommandのCanExecuteの結果を反映する
    /// CommandParameterにValueConverterを指定することでTriggerから受け取るパラメータを利用できるようにする
    /// InvokeCommandActionの機能強化版
    /// </summary>
    /// <remarks>
    ///     <list type="bullet">
    ///         <item>
    ///             CommandParameterプロパティにSystem.Windows.Data.IValueConverter型がセットされた場合は
    ///             Triggerから受け取るパラメータをそのValueConverterで変換してCommandに渡します。
    ///             それ以外はそのままの値をCommandに渡します。
    ///         </item>
    ///         <item>
    ///             IValueConverterがCommandParameterにセットされている場合は
    ///             Eventが発生するまで正しい実行可否が判定できないのでAssociatedObjectのIsEnabledへの適用は行われません。
    ///         </item>
    ///     </list>
    /// </remarks>
#if !SILVERLIGHT
    public class ExtendedInvokeCommandAction : TriggerAction<FrameworkElement>
#else
    public class ExtendedInvokeCommandAction : TriggerAction<Control>
#endif
    {
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register(
                "Command", 
                typeof(ICommand), 
                typeof(ExtendedInvokeCommandAction), 
                new PropertyMetadata(
                    (s, e) => ((ExtendedInvokeCommandAction)s).CommandChanged(e)));

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register(
                "CommandParameter",
                typeof(object),
                typeof(ExtendedInvokeCommandAction),
                new PropertyMetadata(null));

        public static readonly DependencyProperty IsApplyEnableProperty =
            DependencyProperty.Register(
                "IsApplyEnable",
                typeof(bool),
                typeof(ExtendedInvokeCommandAction),
                new PropertyMetadata(true,
                    (s, e) => ((ExtendedInvokeCommandAction)s).IsApplyEnableChanged(e)));

        private EventHandler CanExecuteEventHandler;

        public ExtendedInvokeCommandAction()
        {
            this.CanExecuteEventHandler = this.Command_CanExecuteChanged;
        }

        /// <summary>
        /// 呼び出すコマンド
        /// </summary>
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        /// コマンドパラメータ
        /// </summary>
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// コマンドのCanExecuteの結果をAssociateObjectのEnabledに反映するかどうか
        /// </summary>
        public bool IsApplyEnable
        {
            get { return (bool)GetValue(IsApplyEnableProperty); }
            set { SetValue(IsApplyEnableProperty, value); }
        }

        /// <summary>
        /// アクションを起動します。
        /// </summary>
        /// <param name="parameter">Triggerから渡されるパラメータ</param>
        protected override void Invoke(object parameter)
        {
            var converter = this.CommandParameter as IValueConverter;
            var commandParameter = converter != null ?
                    converter.Convert(
                    parameter,
                    typeof(object),
                    null,
                    Thread.CurrentThread.CurrentCulture) :
                    this.CommandParameter;

            if (this.Command.CanExecute(commandParameter))
            {
                this.Command.Execute(commandParameter);
            }
        }

        private void CommandChanged(DependencyPropertyChangedEventArgs e)
        {
            var oldValue = e.OldValue as ICommand;
            var newValue = e.NewValue as ICommand;

            if (oldValue != null)
            {
                oldValue.CanExecuteChanged -= this.CanExecuteEventHandler;
            }

            if (newValue != null && this.IsApplyEnable)
            {
                newValue.CanExecuteChanged += this.CanExecuteEventHandler;
            }
        }

        private void IsApplyEnableChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.Command == null)
            {
                return;
            }

            this.Command.CanExecuteChanged -= this.CanExecuteEventHandler;

            if ((bool)e.NewValue)
            {
                this.Command.CanExecuteChanged += this.CanExecuteEventHandler;
            }
        }

        private void Command_CanExecuteChanged(object sender, EventArgs e)
        {
            if (this.AssociatedObject == null)
            {
                return;
            }

            // IValueConverterがCommandParameterにセットされている場合は
            // Eventが発生するまで正しい実行可否が判定できないのでAssociatedObjectのIsEnabledへの適用は行わない。
            if (this.IsApplyEnable && !(this.CommandParameter is IValueConverter))
            {
                this.AssociatedObject.IsEnabled = this.Command.CanExecute(this.CommandParameter);
            }
        }
    }
}
